package com.lblin.method.util.monitor;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.lblin.method.annotation.HaveMethod;
import com.lblin.method.annotation.Monitor;
import com.lblin.method.body.BaseBody;
import com.lblin.method.util.ClassPathCandidateComponentScanner;
import com.xiaoleilu.hutool.io.FileUtil;

/**
 * 自检监视类，作用为：关联主体类（使用方法的主体）和方法，监控关联中存在的异常
 * @author vi
 *
 */

public class MethodMonitor {
	
	static boolean useSpring = false;//推荐为true
	
	static boolean createEnum = false;
	
	public static void init(String[] basePackage) {
		init(basePackage,false);
	}

	public static void init(String[] basePackage,boolean isCreate){
		
		createEnum = isCreate;
		
		System.out.println("方法整理框架--start clean up");
		
		Set<Class<?>> classes = new LinkedHashSet<>();
		
		//扫描加载类
		classes = loadClasses(basePackage);
		
		try {
			//扫描类处理注解
			doAnnotaionSearch(classes);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		//开始自动处理Monitor注解标注的类，进行方法拣选
		availableMethod();
		//打印关系树
		//printRelative();
		
		//销毁无用对象
		//destory();
		
		System.out.println("方法整理框架--clean up success!!!");
	}
	
	public static void destory() {
		bodyMethods = null;
		bodyAlias = null;
		methodBodys = null;
		methodAlias = null;
	}

	/**
	 * 打印关系树
	 */
	public static void printRelative() {
		
		printBodyLinkToMethod(null);
		
		printMethodLinkToBody(null,null);
		
	}
	
    /**
     *  查询方法关联的使用者有哪些
     */
	public static void printMethodLinkToBody(String methodName,Class<?> methodClassName) {
		
		if(!methodBodys.isEmpty()) {
		
			System.out.println("-------------关系树↓-------------");
			int index  =  1;
			boolean end = false;
			for(String classType : methodBodys.keySet()) {
				
				if(methodName!=null&&methodClassName!=null) {
					if(classType.equals(methodClassName.getTypeName()+":"+methodName)) {
						end = true;
					}else {
						continue;
					}
				}
				
				System.out.println("-------------"+index+"-------------");
				int size = methodBodys.get(classType).size();
				int cindex = 0;
				for(String body : methodBodys.get(classType)) {
					
					String bodyDesc = bodyAlias.get(body)==null?body:bodyAlias.get(body);
					String methodDesc = methodAlias.get(classType)==null?classType:methodAlias.get(classType);
					
					if(cindex == size/2) {
						System.out.println(methodDesc+":│─────────"+bodyDesc);
					}else if(cindex == 0) {
						for(int j=0;j<methodDesc.length();j++) {
							System.out.print(" ");
						}
						System.out.println(" ┌─────────"+bodyDesc);
					}else if(cindex == size-1) {
						for(int j=0;j<methodDesc.length();j++) {
							System.out.print(" ");
						}
						System.out.println(" └─────────"+bodyDesc);
					}else {
						for(int j=0;j<methodDesc.length();j++) {
							System.out.print(" ");
						}
						System.out.println(" │─────────"+bodyDesc);
					}
					
					cindex++;
				}
				index++;
				
				if(end) {
					break;
				}
			}
		}
	}

	/**
     *  查询类关联的可用方法有哪些
     */
	public static void printBodyLinkToMethod(Class<?> bodyClass) {
		if(!bodyMethods.isEmpty()) {
			System.out.println("-------------关系树↓-------------");
			int index  =  1;
			boolean end = false;
			for(String classType : bodyMethods.keySet()) {
				
				if(bodyClass!=null) {
					if(classType.equals(bodyClass.getTypeName())) {
						end = true;
					}else {
						continue;
					}
				}
				
				System.out.println("-------------"+index+"-------------");
				int size = bodyMethods.get(classType).size();
				int cindex = 0;
				for(String method : bodyMethods.get(classType)) {
					
					String methodDesc = methodAlias.get(method)==null?method:methodAlias.get(method);
					String bodyDesc = bodyAlias.get(classType)==null?classType:bodyAlias.get(classType);
					
					if(cindex == size/2) {
						System.out.println(bodyDesc+":│─────────"+methodDesc);
					}else if(cindex == 0) {
						for(int j=0;j<bodyDesc.length();j++) {
							System.out.print(" ");
						}
						System.out.println(" ┌─────────"+methodDesc);
					}else if(cindex == size-1) {
						for(int j=0;j<bodyDesc.length();j++) {
							System.out.print(" ");
						}
						System.out.println(" └─────────"+methodDesc);
					}else {
						for(int j=0;j<bodyDesc.length();j++) {
							System.out.print(" ");
						}
						System.out.println(" │─────────"+methodDesc);
					}
					
					cindex++;
				}
				index++;
				
				if(end) {
					break;
				}
			}
		}
	}

	private static void availableMethod() {
		for(Class<? extends BaseBody> key : bodys.keySet()) {
			//对象key 选择了一个方法
			List<String> methods = bodyMethods.get(key.getTypeName());
			for(String m : methods) {
				String[] method = m.split(":");
				bodys.get(key).addMethod(method[0], method[1], methodAlias.get(m));
			}
			//生成body类的内部枚举类
			if(createEnum) {
				createBodyEnum(key,methods);
			}
		}
	}
	
	static Pattern p = Pattern.compile("/\\*create-enum\\*/");

	//生成body类的内部枚举类
	private static void createBodyEnum(Class<? extends BaseBody> key, List<String> methods) {
		    //获取项目根目录
			URL url = MethodMonitor.class.getResource("/");
			String path = url.getPath().replace("target/classes", "src/main/java")+key.getTypeName().replace(".", "/")+".java";
			File file = new File(path);
			if(file.exists()) {
				
				String enumContent = createEnumContent(methods);
				String content = FileUtil.readString(file, "UTF-8");
				
				Matcher m = p.matcher(content);
				
				if(m.find()) {
					String result = m.replaceFirst(enumContent);
					FileUtil.writeString(result, file, "UTF-8");
				}
				
			}
	}

	private static String createEnumContent(List<String> methods) {
		StringBuilder sb = new StringBuilder("public enum MyMethodEnum{\n");
		int index = 1;
		for(String m : methods) {
			String[] method = m.split(":");
			sb.append("	 /**").append("\n")
			.append("	  * "+methodAlias.get(m)).append("\n")
			.append("	  */").append("\n")
			.append("	 METHOD"+index+"(\""+method[0]+"\",\""+method[1]+"\",\""+methodAlias.get(m)+"\")");
			if(index<methods.size()) {
				sb.append(",").append("\n");
			}else {
				sb.append(";").append("\n");
			}
			index++;
		}
		
		sb.append("	 private String target;").append("\n")
		.append("	 private String method;").append("\n")
		.append("	 private String desc;").append("\n")
		.append("	 private MyMethodEnum(String target,String method,String desc) {").append("\n")
		.append("	 	this.target = target;").append("\n")
		.append("	 	this.method = method;").append("\n")
		.append("	 	this.desc = desc;").append("\n")
		.append("	 }").append("\n")
		.append("	 public String getTarget() {").append("\n")
		.append("	 	return target;").append("\n")
		.append("	 }").append("\n")
		.append("	 public void setTarget(String target) {").append("\n")
		.append("	 	this.target = target;").append("\n")
		.append("	 }").append("\n")
		.append("	 public String getMethod() {").append("\n")
		.append("	 	return method;").append("\n")
		.append("	 }").append("\n")
		.append("	 public void setMethod(String method) {").append("\n")
		.append("	 	this.method = method;").append("\n")
		.append("	 }").append("\n")
		.append("	 public String getDesc() {").append("\n")
		.append("	 	return desc;").append("\n")
		.append("	 }").append("\n")
		.append("	 public void setDesc(String desc) {").append("\n")
		.append("	 	this.desc = desc;").append("\n")
		.append("	 }").append("\n")
		.append("	 @Override").append("\n")
		.append("	 public String toString() {").append("\n")
		.append("	 	return getTarget()+\":\"+getMethod();").append("\n")
		.append("	 }").append("\n");
		
		sb.append("	}");
		return sb.toString();
	}

	// TODO
	private static Set<Class<?>> loadClasses(String[] basePackage) {
		Set<Class<?>> classes = new LinkedHashSet<>();
		ClassPathCandidateComponentScanner scanner = new ClassPathCandidateComponentScanner();
		if(!useSpring) {//手动扫描包
			for(String packageName : basePackage) {
				try {
					classes.addAll(scanner.findCandidateComponents(packageName));
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return classes;
	}

	/**
	 * 类的可用方法集合
	 */
	private static Map<String,List<String>> bodyMethods = new HashMap<String,List<String>>();
	
	private static Map<String,String> bodyAlias = new HashMap<String,String>();
	
	/**
	 * 方法的作用域 （使用者类的集合）
	 */
	private static Map<String,List<String>> methodBodys = new HashMap<String,List<String>>();
	
	private static Map<String,String> methodAlias = new HashMap<String,String>();
	
	/**
	 * 单例类（可以从spring处获得）
	 */
	public static Map<Class<? extends BaseBody>,BaseBody> bodys = new HashMap<Class<? extends BaseBody>,BaseBody>();

	@SuppressWarnings("unchecked")
	private static void doAnnotaionSearch(Set<Class<?>> classes) throws Exception, Exception {
		for(Class<?> clazz : classes) {
			Monitor mn = clazz.getAnnotation(Monitor.class);
			HaveMethod md = clazz.getAnnotation(HaveMethod.class);
			if(mn!=null) {//方法执行者
				Class<BaseBody> basebodyclazz = (Class<BaseBody>) clazz;
				BaseBody body = newInstance(basebodyclazz);
				bodys.put(basebodyclazz,body);
				if(!mn.name().equals("")) {
					bodyAlias.put(clazz.getTypeName(), mn.name());
				}
				doAnnotaionMethodSearch(clazz);//如果方法集合类就是方法执行者类
			}else if(md!=null) {//方法集合体类
				doAnnotaionMethodSearch(clazz);
			}
		}
	}
	
	// TODO
	private static BaseBody newInstance(Class<BaseBody> clazz) throws Exception {
		if(!useSpring) {
			return clazz.newInstance();
		}
		return null;
	}

	private static void doAnnotaionMethodSearch(Class<?> clazz) {
		
		Method[] methods = clazz.getDeclaredMethods();
		for(Method m : methods) {
			com.lblin.method.annotation.Method md = m.getAnnotation(com.lblin.method.annotation.Method.class);
			if(md!=null) {//开始解析注解处理
				String key = clazz.getTypeName() + ":" + m.getName();
				if(!"".equals(md.desc())) {
					methodAlias.put(key, md.desc());
				}
				if(methodBodys.get(key)==null) {
					methodBodys.put(key, new ArrayList<String>());
				}else {
					throw new MonitorException("同一个类中被标注了@Method注解的方法不可同名");
				}
				
				Class<?>[] users =  md.user();
				for(Class<?> user : users) {
					methodBodys.get(key).add(user.getTypeName());
					
					if(bodyMethods.get(user.getTypeName())!=null) {
						bodyMethods.get(user.getTypeName()).add(key);
					}else {
						bodyMethods.put(user.getTypeName(),new ArrayList<String>());
						bodyMethods.get(user.getTypeName()).add(key);
					}
				}
			}
		}
	}
	
	@SuppressWarnings("serial")
	static class MonitorException extends RuntimeException{
		public MonitorException(String message) {
			super(message);
		}
	}
}
